1//////////////////////////////////////////////////////////////////////
2// LibFile: masks2d.scad
3// This file provides 2D masking shapes that you can use with {{edge_profile()}} to mask edges.
4// The shapes include the simple roundover and chamfer as well as more elaborate shapes
5// like the cove and ogee found in furniture and architecture. You can make the masks
6// as geometry or as 2D paths.
7// Includes:
8// include <BOSL2/std.scad>
9// FileGroup: Basic Modeling
10// FileSummary: 2D masking shapes for edge profiling: including roundover, cove, teardrop, ogee.
11// FileFootnotes: STD=Included in std.scad
12//////////////////////////////////////////////////////////////////////
13
14
15function _inset_corner(corner, mask_angle, inset, excess, flat_top) =
16 let(
17 vertex = [inset.x/sin(mask_angle)+inset.y/tan(mask_angle), inset.y],
18 corner = move(vertex, corner),
19 outside = [
20 [corner[2].x,-excess],
21 [-(excess)/tan(mask_angle/2), -excess],
22 if (!flat_top) corner[0] + polar_to_xy(inset.x+excess,90+mask_angle),
23 if (flat_top) corner[0] - [(excess+inset.x)/sin(mask_angle),0]
24 ]
25 )
26 [outside, corner];
27
28
29
30// Section: 2D Masking Shapes
31
32// Function&Module: mask2d_roundover()
33// Synopsis: Creates a circular mask shape for rounding edges or beading.
34// SynTags: Geom, Path
35// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
36// See Also: corner_profile(), edge_profile(), face_profile(), fillet()
37// Usage: As module
38// mask2d_roundover(r|d=|h=|height=|cut=|joint=, [inset], [mask_angle], [excess], [flat_top=], [quarter_round=]) [ATTACHMENTS];
39// Usage: As function
40// path = mask2d_roundover(r|d=|h=|height=|cut=|joint=, [inset], [mask_angle], [excess], [flat_top=], [quarter_round=]);
41// Description:
42// Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for an edge.
43// Conversely, you can use that same extruded shape to make an interior fillet between two walls.
44// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
45// If called as a function, returns a 2D path of the outline of the mask shape.
46// .
47// The roundover can be specified by radius, diameter, height, cut, or joint length.
48// 
49// .
50// If you need roundings to agree on edges of different mask_angle, e.g. to round the base of a prismoid, then you need all of the
51// masks used to have the same height. (Note that it may appear that matching joint would also work, but it does not because the joint distances are measured
52// in different directions.) You can get the same height by setting the `height` parameter, which is an alternate way to control the size of the rounding.
53// You can also set `quarter_round=true`, which creates a rounding that uses a quarter circle of the specified radius for all mask angles. If you have set inset
54// you will need `flat_top=true` as well. Note that this is the default if you use `quarter_round=true` but not otherwise. Generally if you want a roundover
55// results are best using the `height` option but if you want a bead as you get using `inset` the results are often best using the `quarter_round=true` option.
56// Arguments:
57// r = Radius of the roundover.
58// inset = Optional bead inset size, perpendicular to the two edges. Scalar or 2-vector. Default: 0
59// mask_angle = Number of degrees in the corner angle to mask. Default: 90
60// excess = Extra amount of mask shape to creates on the X and quasi-Y sides of the shape. Default: 0.01
61// ---
62// d = Diameter of the roundover.
63// h / height = Mask height excluding inset and excess. Give instead of r / d, cut or joint when you want a consistent mask height, no matter what the mask angle.
64// cut = Cut distance. IE: How much of the corner to cut off. See [Types of Roundovers](rounding.scad#section-types-of-roundovers).
65// joint = Joint distance. IE: How far from the edge the roundover should start. See [Types of Roundovers](rounding.scad#section-types-of-roundovers).
66// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true if quarter_round is set, false otherwise.
67// quarter_round = If true, make a roundover independent of the mask_angle, defined based on a quarter circle of the specified size. Creates mask with angle-independent height. Default: false.
68// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
69// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
70// Side Effects:
71// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
72//
73// Example(2D): 2D Roundover Mask by Radius
74// mask2d_roundover(r=10);
75// Example(2D): 2D Bead Mask
76// mask2d_roundover(r=10,inset=2);
77// Example(2D): 2D Roundover Mask by Radius, acute angle
78// mask2d_roundover(r=10, mask_angle=50);
79// Example(2D): 2D Bead Mask by Radius, acute angle
80// mask2d_roundover(r=10, inset=2, mask_angle=50);
81// Example(2D): 2D Bead Mask for obtuse angle, by height
82// mask2d_roundover(h=10, inset=2, mask_angle=135, $fn=64);
83// Example(2D): 2D Bead Mask for obtuse angle, by height with flat top
84// mask2d_roundover(h=10, inset=2, mask_angle=135, flat_top=true, $fn=64);
85// Example(2D): 2D Angled Bead Mask by Joint Length. Joint length does not include the inset.
86// mask2d_roundover(joint=10, inset=2, mask_angle=75);
87// Example(2D): Increasing the Excess
88// mask2d_roundover(r=10, inset=2, mask_angle=75, excess=2);
89// Example(2D): quarter_round bead on an acute angle
90// mask2d_roundover(r=10, inset=2, mask_angle=50, quarter_round=true);
91// Example(2D): quarter_round bead on an obtuse angle
92// mask2d_roundover(r=10, inset=2, mask_angle=135, quarter_round=true);
93// Example: Masking by Edge Attachment
94// diff()
95// cube([50,60,70],center=true)
96// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
97// mask2d_roundover(h=12, inset=2);
98// Example: Making an interior fillet
99// %render() difference() {
100// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
101// cube(310, anchor=BOT+LEFT);
102// }
103// xrot(90)
104// linear_extrude(height=30, center=true)
105// mask2d_roundover(r=10);
106// Example(3D,Med,VPT=[25,30,12],VPR=[68,0,12],VPD=180): Rounding over top of an extreme prismoid using height option
107// diff()
108// prismoid([30,20], [50,60], h=20, shift=[40,50])
109// edge_profile(TOP, excess=27)
110// mask2d_roundover(height=5, mask_angle=$edge_angle, $fn=128);
111// Example(3D,Med,VPT=[25,30,12],VPR=[68,0,12],VPD=180): Using the quarter_round option results in a lip on obtuse angles, so it may not be the best choice for pure roundings.
112// diff()
113// prismoid([30,20], [50,60], h=20, shift=[40,50])
114// edge_profile(TOP, excess=27)
115// mask2d_roundover(r=5, mask_angle=$edge_angle, quarter_round=true, $fn=128);
116// // Example(3D,Med,VPT=[25,30,12],VPR=[68,0,12],VPD=180): Can improve the quarter round option by using it only for acute angles and falling back on regular rounding for obtuse angles. Note that in this case, obtuse angles are fully rounded, but acute angles still have a corner, but one that is not as sharp as the original angle.
117// diff()
118// prismoid([30,20], [50,60], h=20, shift=[40,50])
119// edge_profile(TOP, excess=27)
120// mask2d_roundover(r=5, mask_angle=$edge_angle, quarter_round=$edge_angle<90, $fn=32);
121// Example(3D,Med,VPT=[25,30,12],VPR=[68,0,12],VPD=180): Creating a bead on the prismoid using the height option with flat_top=true:
122// diff()
123// prismoid([30,20], [50,60], h=20, shift=[40,50])
124// edge_profile(TOP, excess=27)
125// mask2d_roundover(height=5, mask_angle=$edge_angle, inset=1.5, flat_top=true, $fn=128);
126// Example(3D,Med,VPT=[25,30,12],VPR=[68,0,12],VPD=180): Bead may be more pleasing using the quarter_round option, with curves terminating in a plane parallel to the prismoid top. The size of the inset edge will be larger than requested when the angle is obtuse.
127// diff()
128// prismoid([30,20], [50,60], h=20, shift=[40,50])
129// edge_profile(TOP, excess=27)
130// mask2d_roundover(r=5, mask_angle=$edge_angle, quarter_round=true, inset=1.5, $fn=128);
131module mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, flat_top, d, h, height, cut, quarter_round=false, joint, anchor=CENTER,spin=0) {
132 path = mask2d_roundover(r=r, d=d, h=h, height=height, cut=cut, joint=joint, inset=inset,
133 flat_top=flat_top, mask_angle=mask_angle, excess=excess, quarter_round=quarter_round);
134 default_tag("remove") {
135 attachable(anchor,spin, two_d=true, path=path) {
136 polygon(path);
137 children();
138 }
139 }
140}
141
142
143function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, flat_top, quarter_round=false, d, h, height, cut, joint, anchor=CENTER, spin=0) =
144 assert(one_defined([r,height,d,h,cut,joint],"r,height,d,h,cut,joint"))
145 assert(all_nonnegative([excess]), "excess must be a nonnegative value")
146 assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180)
147 assert(is_finite(inset)||is_vector(inset,2))
148 assert(is_bool(quarter_round))
149 let(flat_top=default(flat_top, quarter_round))
150 assert(is_bool(flat_top))
151 let(
152 inset = is_list(inset)? inset : [inset,inset],
153 r = get_radius(r=r,d=d,dflt=undef),
154 dummy2=assert(is_def(r) || !quarter_round,"Must give r / d when quarter_round is true"),
155 h = u_add(one_defined([h,height],"h,hight",dflt=undef),flat_top || mask_angle>=90?0:-inset.x*cos(mask_angle)),
156 // compute [joint length, radius] for different types of input
157 jr = is_def(h) ? assert(all_positive([h]), "height / h must be larger than y inset")
158 h/sin(mask_angle)*[1,tan(mask_angle/2)]
159 : is_def(r) ? assert(all_positive([r]), "r / d must be a positive value")
160 [r/tan(mask_angle/2), r]
161 : is_def(joint) ? assert(all_positive([joint]), "joint must be a positive value")
162 joint*[1, tan(mask_angle/2)]
163 : assert(all_positive([cut]),"cut must be a positive value")
164 let(circ_radius=cut/(1/sin(mask_angle/2)-1))
165 [circ_radius/tan(mask_angle/2), circ_radius],
166 dist=jr[0],
167 radius=jr[1],
168 quarter_round_top = approx(mask_angle,90) ? 0
169 : radius/tan(mask_angle),
170 extra = radius/20, // Exact solution is tangent, which will make bad geometry, so insert an offset factor
171 quarter_round_shift = !quarter_round || mask_angle<=90 ? 0
172 : radius/sin(180-mask_angle)-radius+extra,
173 outside_corner = _inset_corner(
174 quarter_round ?
175 [
176 [quarter_round_top,radius],
177 [0,0],
178 [radius+quarter_round_top+quarter_round_shift,0]
179 ]
180 :
181 [
182 dist*[cos(mask_angle),sin(mask_angle)],
183 [0,0],
184 [dist,0]
185 ],
186 mask_angle, inset, excess, flat_top),
187 // duplicates arise at one or both ends if excess and inset are both zero there
188 cornerpath = !quarter_round ? outside_corner[1]
189 : mask_angle<=90 ? outside_corner[1]+[[0,0],[quarter_round_top,0],[0,0]]
190 : [ outside_corner[1][0]+[quarter_round_shift,0],
191 [outside_corner[1][0].x+quarter_round_shift,inset.y],
192 outside_corner[1][2]
193 ],
194 dummy=assert(last(cornerpath).x>=0,str("inset.y is too large to fit roundover at angle ",mask_angle)),
195 path = deduplicate([
196 each outside_corner[0],
197 outside_corner[1][0],
198 each arc(corner=cornerpath, r=radius),
199 outside_corner[1][2]
200 ]
201 ,closed=true)
202 ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
203
204
205
206
207// Function&Module: mask2d_teardrop()
208// Synopsis: Creates a 2D teardrop shape with specified max angle from vertical.
209// SynTags: Geom, Path
210// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D), FDM Optimized
211// See Also: corner_profile(), edge_profile(), face_profile()
212// Usage: As Module
213// mask2d_teardrop(r|d=, [angle], [inset] [mask_angle], [excess], [cut=], [joint=], [h=|height=]) [ATTACHMENTS];
214// Usage: As Function
215// path = mask2d_teardrop(r|d=, [angle], [inset], [mask_angle], [excess], [cut=], [joint=], [h=|height=]);
216// Description:
217// Creates a 2D teardrop mask shape that is useful for extruding into a 3D mask for an edge.
218// Conversely, you can use that same extruded shape to make an interior teardrop fillet between two walls.
219// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
220// If called as a function, returns a 2D path of the outline of the mask shape.
221// This is particularly useful to make partially rounded bottoms, that don't need support to print.
222// The roundover can be specified by radius, diameter, height, cut, or joint length.
223// 
224// Arguments:
225// r = Radius of the rounding.
226// angle = The angle from vertical of the flat section. Must be between mask_angle-90 and 90 degrees. Default: 45.
227// inset = Optional bead inset size perpendicular to edges. Default: 0
228// mask_angle = Number of degrees in the corner angle to mask. Default: 90
229// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01
230// ---
231// d = Diameter of the rounding.
232// h / height = Mask height excluding inset and excess. Given instead of r or d when you want a consistent mask height, no matter what the mask angle.
233// cut = Cut distance. IE: How much of the corner to cut off. See [Types of Roundovers](rounding.scad#section-types-of-roundovers).
234// joint = Joint distance. IE: How far from the edge the roundover should start. See [Types of Roundovers](rounding.scad#section-types-of-roundovers).
235// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true.
236// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
237// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
238// Side Effects:
239// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
240// Example(2D): 2D Teardrop Mask
241// mask2d_teardrop(r=10,$fn=64);
242// Example(2D): 2D Teardrop Mask for acute angle
243// mask2d_teardrop(r=10, mask_angle=75,$fn=64);
244// Example(2D): 2D Teardrop Mask for obtuse angle, specifying height
245// mask2d_teardrop(h=10, mask_angle=115,$fn=128);
246// Example(2D): Increasing Excess
247// mask2d_teardrop(r=10, mask_angle=75, excess=2);
248// Example(2D): Using a Custom Angle
249// mask2d_teardrop(r=10,angle=30,$fn=128);
250// Example(2D): With an acute mask_angle you can choose an angle of zero:
251// mask2d_teardrop(r=10,mask_angle=44,angle=0);
252// Example(2D): With an acute mask_angle you can even choose a negative angle
253// mask2d_teardrop(r=10,mask_angle=44,angle=-15);
254// Example(2D): With an obtuse angle you need to choose a larger angle. Here we add inset.
255// mask2d_teardrop(h=10, mask_angle=135,angle=60, inset=2);
256// Example(2D): Same thing with `flat_top=true`.
257// mask2d_teardrop(h=10, mask_angle=135,angle=60, inset=2, flat_top=true);
258// Example: Masking by Edge Attachment
259// diff()
260// cube([50,60,70],center=true)
261// edge_profile(BOT)
262// mask2d_teardrop(r=10, angle=40);
263// Example: Making an interior teardrop fillet
264// %render() difference() {
265// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
266// cube(310, anchor=BOT+LEFT);
267// }
268// xrot(90)
269// linear_extrude(height=30, center=true)
270// mask2d_teardrop(r=10);
271
272function mask2d_teardrop(r, angle=45, inset=[0,0], mask_angle=90, excess=0.01, flat_top=false, d, h, height, cut, joint, anchor=CENTER, spin=0) =
273 assert(one_defined([r,height,d,h,cut,joint],"r,height,d,h,cut,joint"))
274 assert(is_finite(angle) && angle>mask_angle-90 && angle<90)
275 assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180)
276 assert(all_nonnegative([excess]), "excess must be a nonnegative value")
277 assert(is_finite(inset)||is_vector(inset,2))
278 assert(is_bool(flat_top))
279 let(
280 inset = is_list(inset)? inset : [inset,inset],
281 r = get_radius(r=r,d=d,dflt=undef),
282 h = one_defined([h,height],"h,hight",dflt=undef),
283 // compute [joint length, radius] for different types of input
284 jr = is_def(h) ? assert(all_positive([h]), "height / h must be a positive value")
285 (flat_top ? (h+inset.x*cos(mask_angle))/sin(mask_angle)*[1,tan(mask_angle/2)]
286 : h/sin(mask_angle)*[1,tan(mask_angle/2)])
287 : is_def(r) ? assert(all_positive([r]), "r / d must be a positive value")
288 [r/tan(mask_angle/2), r]
289 : is_def(joint) ? assert(all_positive([joint]), "joint must be a positive value")
290 joint*[1, tan(mask_angle/2)]
291 : assert(all_positive([cut]),"cut must be a positive value")
292 let(circ_radius=cut/(1/sin(mask_angle/2)-1))
293 [circ_radius/tan(mask_angle/2), circ_radius],
294 dist=jr[0],
295 radius=jr[1],
296 outside_corner = _inset_corner(
297 [
298 dist*[cos(mask_angle),sin(mask_angle)],
299 [0,0],
300 [dist,0]
301 ],
302 mask_angle, inset, excess, flat_top),
303
304 arcpts = arc(r=radius, corner=outside_corner[1]),
305 arcpts2 = [
306 for (i = idx(arcpts))
307 if(i==0 || v_theta(arcpts[i]-arcpts[i-1]) <= angle-90)
308 arcpts[i]
309 ],
310 line1 = [last(arcpts2), last(arcpts2) + polar_to_xy(1, angle-90)],
311 line2 = [[0,inset.y], [100,inset.y]],
312 ipt = line_intersection(line1,line2),
313 path = deduplicate([
314 [ipt.x, -excess],
315 each select(outside_corner[0],1,-1),
316 each arcpts2,
317 ipt
318 ], closed=true)
319 ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
320
321
322
323module mask2d_teardrop(r, angle=45, mask_angle=90, excess=0.01, inset=0, flat_top=false, height, d, h, cut, joint, anchor=CENTER, spin=0) {
324 path = mask2d_teardrop(r=r, d=d, h=h, height=height, flat_top=flat_top, cut=cut, joint=joint, angle=angle,inset=inset, mask_angle=mask_angle, excess=excess);
325 default_tag("remove") {
326 attachable(anchor,spin, two_d=true, path=path) {
327 polygon(path);
328 children();
329 }
330 }
331}
332
333
334
335
336// Function&Module: mask2d_cove()
337// Synopsis: Creates a 2D cove (quarter-round) mask shape.
338// SynTags: Geom, Path
339// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
340// See Also: corner_profile(), edge_profile(), face_profile()
341// Usage: As module
342// mask2d_cove(r|d=|h=|height=, [inset], [mask_angle], [excess], [bulge=], [flat_top=], [quarter_round=]) [ATTACHMENTS];
343// Usage: As function
344// path = mask2d_cove(r|d=|h=, [inset], [mask_angle], [excess], [bulge=], [flat_top=]);
345// Description:
346// Creates a 2D cove mask shape that is useful for extruding into a 3D mask for an edge.
347// Conversely, you can use that same extruded shape to make an interior rounded shelf decoration between two walls.
348// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
349// If called as a function, returns a 2D path of the outline of the mask shape.
350// .
351// If you need coves to agree on edges of different mask_angle, e.g. on the top of a prismoid, then you need all of the
352// masks used to have the same height. You can get the same height by setting the `height` parameter. For obtuse angles, however, the cove mask may not
353// have is maximum height at the edge, which means it won't mate with adjacent coves. You can fix this using `flat_top=true` which extends the circle
354// with a line to maintain a flat top. Another way to fix it is to set `bulge`. You can also achieve constant height using the `quarter_round=` option,
355// which uses a quarter circle of the specified size for all mask_angle values. This option often produces a nice result because coves all terminate in a
356// plane at 90 degrees.
357// Arguments:
358// r = Radius of the cove.
359// inset = Optional amount to inset in the perpendicular direction from the edges. Scalar or 2-vector. Default: 0
360// mask_angle = Number of degrees in the corner angle to mask. Default: 90
361// excess = Extra amount of mask shape to creates on the X and quasi-Y sides of the shape. Default: 0.01
362// ---
363// d = Diameter of the cove.
364// h / height = Mask height, excluding inset and excess. Given instead of r or d when you want a consistent mask height, no matter what the mask angle.
365// bulge = specify arc as the distance away from a straight line chamfer. The arc will not meet the sides at a 90 deg angle.
366// quarter_round = If true, make cove independent of the mask_angle, defined based on a quarter circle, with angle-independent radius. The mask will have constant height. Default: false.
367// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. In the case of obtuse angles force the mask to have a flat section at its left side instead of a circular arc. Default: true if quarter_round is set, false otherwise.
368// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
369// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
370// Side Effects:
371// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
372// Example(2D): 2D Cove Mask by Radius
373// mask2d_cove(r=10);
374// Example(2D): 2D Inset Cove Mask (not much different than a regular cove of larger radius)
375// mask2d_cove(r=10,inset=3);
376// Example(2D): 2D Cove Mask for acute angle, specified by height, with the bulge set to change the curve. Note that the circular arc is not perpendicular to the sides.
377// mask2d_cove(h=10,mask_angle=55, bulge=3);
378// Example(2D): 2D Cove Mask for obtuse angle, specified by height. This will produce an odd result if combined with other masks because the maximum height is in the middle.
379// mask2d_cove(h=10,mask_angle=145);
380// Example(2D): 2D Cove Mask for obtuse angle with flat top. This is one solution to the problem of the previous example. Max height is achieved at the left corner.
381// mask2d_cove(h=10,mask_angle=145,flat_top=true);
382// Example(2D): 2D Cove Mask for obtuse angle, specified by height with bulge parameter. Another way to fix the problem of the previous example: the max height is again achieved at the left corner.
383// mask2d_cove(h=10,mask_angle=145, bulge=3, $fn=128);
384// Example(2D): 2D Cove Mask for acute angle with quarter_round enabled
385// mask2d_cove(r=10,mask_angle=55,quarter_round=true);
386// Example(2D): 2D Cove Mask for obtuse angle, specified by height. Note that flat_top is on by default in quarter_round mode.
387// mask2d_cove(r=10,mask_angle=145,quarter_round=true);
388// Example(2D): Increasing the Excess
389// mask2d_cove(r=10,inset=3,mask_angle=75, excess=2);
390// Example: Masking by Edge Attachment
391// diff()
392// cube([50,60,70],center=true)
393// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
394// mask2d_cove(h=10, inset=3);
395// Example: Making an interior rounded shelf
396// %render() difference() {
397// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
398// cube(310, anchor=BOT+LEFT);
399// }
400// xrot(90)
401// linear_extrude(height=30, center=true)
402// mask2d_cove(r=5, inset=5);
403// Example(3D,Med): A cove on top of an extreme prismoid top by setting height and using flat_top mode. This creates **long** flat tops sections at obtuse angles.
404// diff()
405// prismoid([50,60], [20,30], h=20, shift=[25,16])
406// edge_profile(TOP, excess=20)
407// mask2d_cove(h=5, inset=0, mask_angle=$edge_angle, flat_top=true, $fn=128);
408// Example(3D,Med): Cove on an extreme prismoid top by setting height and bulge. Obtuse angles have long **curved** sections.
409// diff()
410// prismoid([50,60], [20,30], h=20, shift=[25,16])
411// edge_profile(TOP, excess=20)
412// mask2d_cove(h=5, inset=0, mask_angle=$edge_angle, bulge=1, $fn=128);
413// Example(3D,Med): Rounding an extreme prismoid top using quarter_round. Another way to handle this situation.
414// diff()
415// prismoid([50,60], [20,30], h=20, shift=[25,16])
416// edge_profile(TOP, excess=20)
417// mask2d_cove(r=5, inset=0, mask_angle=$edge_angle, quarter_round=true, $fn=128);
418
419module mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, flat_top, bulge, d, h, height, quarter_round=false, anchor=CENTER, spin=0) {
420 path = mask2d_cove(r=r, d=d, h=h, height=height, bulge=bulge, flat_top=flat_top, quarter_round=quarter_round, inset=inset, mask_angle=mask_angle, excess=excess);
421 default_tag("remove") {
422 attachable(anchor,spin, two_d=true, path=path) {
423 polygon(path);
424 children();
425 }
426 }
427}
428
429
430function mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, flat_top, d, h, height,bulge, quarter_round=false, anchor=CENTER, spin=0) =
431 assert(one_defined([r,d,h,height],"r,d,h,height"))
432 assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180)
433 assert(is_finite(excess))
434 assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2))
435 assert(is_bool(quarter_round))
436 let(flat_top=default(flat_top,quarter_round))
437 assert(is_bool(flat_top))
438 assert(is_undef(bulge) || all_positive([bulge]),"bulge must be a positive value")
439 let(
440 inset = force_list(inset,2),
441 r = get_radius(r=r,d=d,dflt=undef),
442 h = u_add(one_defined([h,height],"h,hight",dflt=undef),flat_top || mask_angle>=90?0:-inset.x*cos(mask_angle)),
443 radius = is_def(h) ? assert(all_positive([h]), "height / h must be a larger than y inset")
444 !bulge && (quarter_round || mask_angle>90) ? h-inset.y
445 : h/sin(mask_angle)
446 : assert(all_positive([r]), "r / d must be a positive value") r,
447 quarter_round_ofs = quarter_round ? radius/tan(mask_angle) : 0,
448 outside_corner = _inset_corner(
449 quarter_round ?
450 [
451 [quarter_round_ofs,radius],
452 [0,0],
453 [quarter_round_ofs+radius,0]
454 ]
455 : mask_angle>90 && flat_top && is_undef(bulge) ?
456 [
457 [radius/tan(mask_angle),radius],
458 [0,0],
459 [radius,0]
460 ]
461 :
462 [
463 radius*[cos(mask_angle),sin(mask_angle)],
464 [0,0],
465 [radius,0]
466 ],
467 mask_angle, inset, excess, flat_top),
468 quarter_round_big_fix = quarter_round && mask_angle>135 ? quarter_round_ofs+radius
469 : 0,
470 flatfix = !quarter_round && is_undef(bulge) && flat_top && mask_angle>90 ? radius/tan(mask_angle)
471 : 0,
472 corners = select(outside_corner[1], [0,2]) - [[quarter_round_big_fix+flatfix,0],[quarter_round_big_fix,0]],
473 bulgept = is_undef(bulge) ? undef
474 : let(
475 normal = line_normal(corners)
476 )
477 mean(corners)+bulge*normal,
478 dummy=assert(corners[1].x>=0, str("inset.y is too large to fit cove at angle ",mask_angle)),
479 cp = quarter_round ? [corners[0].x,inset.y] : outside_corner[1][1],
480 path = deduplicate([
481 [corners[1].x,-excess],
482 each select(outside_corner[0],1,-1),
483 if (bulge) each arc(points=[corners[0], bulgept, corners[1]]),
484 if (!bulge) each arc(cp=cp, points = corners),
485 ],
486 closed=true)
487 ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
488
489
490
491// Function&Module: mask2d_chamfer()
492// Synopsis: Produces a 2D chamfer mask shape.
493// SynTags: Geom, Path
494// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
495// See Also: corner_profile(), edge_profile(), face_profile()
496// Usage: As Module
497// mask2d_chamfer(edge, [angle], [inset], [excess]) [ATTACHMENTS];
498// mask2d_chamfer(y=, [angle=], [inset=], [excess=]) [ATTACHMENTS];
499// mask2d_chamfer(x=, [angle=], [inset=], [excess=]) [ATTACHMENTS];
500// Usage: As Function
501// path = mask2d_chamfer(edge, [angle], [inset], [excess]);
502// path = mask2d_chamfer(y=, [angle=], [inset=], [excess=]);
503// path = mask2d_chamfer(x=, [angle=], [inset=], [excess=]);
504// Description:
505// Creates a 2D chamfer mask shape that is useful for extruding into a 3D mask for an edge.
506// Conversely, you can use that same extruded shape to make an interior chamfer between two walls.
507// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
508// If called as a function, returns a 2D path of the outline of the mask shape.
509// The edge parameter specifies the length of the chamfer's slanted edge. The x parameter specifies the width. The y parameter
510// specfies the length of the non-horizontal arm of the chamfer. The height specifies the height of the chamfer independent
511// of angle. You can specify any combination of parameters that determines a chamfer geometry.
512// Arguments:
513// edge = The length of the edge of the chamfer.
514// angle = The angle of the chamfer edge, away from vertical. Default: mask_angle/2.
515// inset = Optional amount to inset perpendicular to each edge. Scalar or 2-vector. Default: 0
516// mask_angle = Number of degrees in the corner angle to mask. Default: 90
517// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01
518// ---
519// x = The width of the chamfer (joint distance in x direction)
520// y = The set-back (joint distance) in the non-x direction of the chamfer.
521// h / height = The height of the chamfer (excluding inset and excess).
522// w/ width = The width of the chamfer (excluding inset and excess).
523// quarter_round = If true, make a roundover independent of the mask_angle, defined based on a 90 deg angle, with a constant height. Default: false.
524// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true.
525// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
526// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
527// Side Effects:
528// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
529// Example(2D): 2D Chamfer Mask, at 45 deg by default
530// mask2d_chamfer(x=10);
531// Example(2D): 2D Chamfer Mask, at 30 deg (measured down from vertical)
532// mask2d_chamfer(x=10,angle=30);
533// Example(2D): 2D Chamfer Mask on an acute angle. The default chamfer angle is to produce a symmetric chamfer.
534// mask2d_chamfer(x=10,mask_angle=45);
535// Example(2D): 2D Chamfer Mask on an acute angle. Here we specify the angle of the chamfer
536// mask2d_chamfer(x=10,mask_angle=45,angle=45);
537// Example(2D): 2D Chamfer Mask specified by x and y length
538// mask2d_chamfer(x=4,y=10);
539// Example(2D): 2D Chamfer Mask specified by x and y length. The y length is along the top side of the chamfer, not parallel to the Y axis.
540// mask2d_chamfer(x=4,y=5,mask_angle=44);
541// Example(2D): 2D Chamfer Mask specified by width and height.
542// mask2d_chamfer(w=4,h=5,mask_angle=44);
543// Example(2D): 2D Chamfer Mask on obtuse angle, specifying x. The right tip is 10 units from the origin.
544// mask2d_chamfer(x=10,mask_angle=127);
545// Example(2D): 2D Chamfer Mask on obtuse angle, specifying width. The entire width is 10.
546// mask2d_chamfer(w=10,mask_angle=127);
547// Example(2D): 2D Chamfer Mask by edge
548// mask2d_chamfer(edge=10);
549// Example(2D): 2D Chamfer Mask by edge, acute case
550// mask2d_chamfer(edge=10, mask_angle=44);
551// Example(2D): 2D Chamfer Mask by edge, obtuse case
552// mask2d_chamfer(edge=10, mask_angle=144);
553// Example(2D): 2D Chamfer Mask by edge and angle
554// mask2d_chamfer(edge=10, angle=30);
555// Example(2D): 2D Chamfer Mask by edge and x
556// mask2d_chamfer(edge=10, x=9);
557// Example(2D): 2D Inset Chamfer Mask
558// mask2d_chamfer(x=10, inset=2);
559// Example(2D): 2D Inset Chamfer Mask on acute angle
560// mask2d_chamfer(x=10, inset=2, mask_angle=77);
561// Example(2D): 2D Inset Chamfer Mask on acute angle with flat top
562// mask2d_chamfer(x=10, inset=2, mask_angle=77, flat_top=true);
563// Example: Masking by Edge Attachment
564// diff()
565// cube([50,60,70],center=true)
566// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
567// mask2d_chamfer(x=10, inset=2);
568// Example: Making an interior chamfer
569// %render() difference() {
570// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
571// cube(310, anchor=BOT+LEFT);
572// }
573// xrot(90)
574// linear_extrude(height=30, center=true)
575// mask2d_chamfer(edge=10);
576// Example(3D,Med): Chamfering an extreme prismoid by setting height
577// diff()
578// prismoid([50,60], [20,30], h=20, shift=[25,16])
579// edge_profile(TOP, excess=20)//let(f=$edge_angle)
580// mask2d_chamfer(h=5,mask_angle=$edge_angle);
581// Example(3D,Med): Chamfering an extreme prismoid with a fixed chamfer angle. Note that a very large chamfer angle is required because of the large obtuse angles.
582// diff()
583// prismoid([50,60], [20,30], h=20, shift=[25,16])
584// edge_profile(TOP, excess=20)//let(f=$edge_angle)
585// mask2d_chamfer(h=5,mask_angle=$edge_angle,angle=64);
586// Example(3D,Med): Chamfering an extreme prismoid by setting height with inset and flat_top=true.
587// diff()
588// prismoid([50,60], [20,30], h=20, shift=[25,16])
589// edge_profile(TOP, excess=20)//let(f=$edge_angle)
590// mask2d_chamfer(h=4,inset=1,flat_top=true,mask_angle=$edge_angle);
591
592module mask2d_chamfer(edge, angle, inset=0, excess=0.01, mask_angle=90, flat_top=false, x, y, h, w, height, width, anchor=CENTER,spin=0) {
593 path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, height=height, h=h, excess=excess, w=w,
594 inset=inset, mask_angle=mask_angle, flat_top=flat_top,width=width);
595 default_tag("remove") {
596 attachable(anchor,spin, two_d=true, path=path, extent=true) {
597 polygon(path);
598 children();
599 }
600 }
601}
602
603function mask2d_chamfer(edge, angle, inset=0, excess=0.01, mask_angle=90, flat_top=false, x, y, h, w, width, height, anchor=CENTER,spin=0) =
604 assert(is_undef(x) || all_positive([x]))
605 assert(is_undef(y) || all_positive([y]))
606 assert(is_undef(w) || all_positive([w]))
607 assert(is_undef(h) || all_positive([h]))
608 assert(is_undef(height) || all_positive([height]))
609 assert(is_undef(width) || all_positive([width]))
610 assert(is_undef(edge) || all_positive([edge]))
611 assert(all_nonnegative([excess]))
612 assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180)
613 assert(is_finite(inset)||is_vector(inset,2))
614 assert(is_undef(angle) || angle>mask_angle-90, str("angle must be larger than ",mask_angle-90," for chamfer to fit"))
615 let(
616 inset = is_list(inset)? inset : [inset,inset],
617 h = one_defined([h,height],"h,height",dflt=undef),
618 w = one_defined([w,width],"w,width",dflt=undef),
619 dummy = assert(num_defined([y,h])<=1, "Cannot defined both h / height and y")
620 assert(num_defined([x,w])<=1, "Cannot defined both w / width and x"),
621 y = is_def(h) ? assert(all_positive([h]), "height / h must be postitive")
622 h/sin(mask_angle) : y,
623 xy = is_def(w) ? assert(is_undef(edge), "Cannot combine edge with width")
624 assert(num_defined([y,angle])<=1, "Conflicting values of width, y and angle given")
625 let(
626 angle=default(angle,mask_angle/2),
627 y = is_def(y) ? y
628 : w/tan(angle)
629 )
630 [w+y*cos(mask_angle),y]
631 : is_def(x) ? assert(num_defined([y,edge,angle])<=1, "Conflicting values of x, y, height, edge and angle given")
632 (
633 is_def(y) ? [x,y]
634 : is_def(edge) ? let(yopt=quadratic_roots(1,-2*x*cos(mask_angle), x^2-edge^2,real=true),fff=echo(yopt))
635 assert(yopt!=[] && max(yopt)>0, "edge too short for x value")
636 [x,max(yopt)]
637 : let(angle=default(angle,mask_angle/2))
638 [x,law_of_sines(a=x,A=90-mask_angle+angle,B=90-angle)]
639 )
640 : is_def(y) ? assert(num_defined([edge,angle])<=1, "Conflicting or insufficient values of x, y, height, edge and angle given")
641 (
642 is_def(edge) ? let(xopt=quadratic_roots(1,-2*y,cos(mask_angle), y^2-edge^2,real=true))
643 assert(xopt!=[], "edge too short for y value")
644 [x,max(xopt)]
645 : let(angle=default(angle,mask_angle/2))
646 [law_of_sines(a=y,A=90-angle,B=90-mask_angle+angle), y]
647 )
648 : assert(is_def(edge), "Must give one of x, y, w/width, h/height, or edge")
649 let(angle=default(angle,mask_angle/2))
650 [law_of_sines(a=edge,A=mask_angle, B=90-mask_angle+angle),
651 law_of_sines(a=edge,A=mask_angle, B=90-angle)],
652 dummy3=assert(xy.x > xy.y*cos(mask_angle), str("Chamfer does not fit with mask_angle ",mask_angle)),
653 // These computations are just for the error message...actually only work without inset
654 // ref_pt = polar_to_xy(xy.y, mask_angle),
655 // angle = 90-atan(ref_pt.y/(xy.x-ref_pt.x)),
656 outside_corner = _inset_corner(
657 [
658 polar_to_xy(xy.y,mask_angle),
659 [0,0],
660 [xy.x,0]
661 ],
662 mask_angle, inset, excess, flat_top),
663 dummy2=assert(outside_corner[1][2].x>0,str("Angle of chamfer is too small to fit on mask angle ",mask_angle,
664 ". Either increase angle or add x inset to make space.")),
665 path = deduplicate(concat(outside_corner[0], select(outside_corner[1],[0,2])),closed=true)
666 ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
667
668
669// Function&Module: mask2d_rabbet()
670// Synopsis: Creates a rabbet mask shape.
671// SynTags: Geom, Path
672// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
673// See Also: corner_profile(), edge_profile(), face_profile()
674// Usage: As Module
675// mask2d_rabbet(size, [mask_angle], [excess]) [ATTACHMENTS];
676// Usage: As Function
677// path = mask2d_rabbet(size, [mask_angle], [excess]);
678// Description:
679// Creates a 2D rabbet mask shape. When differenced away, this mask
680// creates at the corner a rectanguler space of the specified size.
681// This mask can be extruding into a 3D mask for an edge, or
682// you can use that same extruded shape to make an interior shelf decoration between two walls.
683// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
684// If called as a function, returns a 2D path of the outline of the mask shape.
685// Arguments:
686// size = The size of the rabbet, either as a scalar or an [X,Y] list.
687// mask_angle = Number of degrees in the corner angle to mask. Default: 90
688// excess = Extra amount of mask shape to creates on the X and quasi-Y sides of the shape. Default: 0.01
689// ---
690// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
691// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
692// Side Effects:
693// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
694// Example(2D): 2D Rabbet Mask
695// mask2d_rabbet(size=10);
696// Example(2D): 2D Asymmetrical Rabbet Mask
697// mask2d_rabbet(size=[5,10]);
698// Example(2D): 2D Mask for a acute angle edge
699// mask2d_rabbet(size=10, mask_angle=75);
700// Example(2D): 2D Mask for obtuse angle edge. If the obtuse angle is too large the rabbet will not fit. If that happens, you will need to increase the rabbet width.
701// mask2d_rabbet(size=10, mask_angle=125);
702// Example: Masking by Edge Attachment
703// diff()
704// cube([50,60,70],center=true)
705// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
706// mask2d_rabbet(size=10);
707// Example: Making an interior shelf
708// %render() difference() {
709// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
710// cube(310, anchor=BOT+LEFT);
711// }
712// xrot(90)
713// linear_extrude(height=30, center=true)
714// mask2d_rabbet(size=[5,10]);
715module mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) {
716 path = mask2d_rabbet(size=size, mask_angle=mask_angle, excess=excess);
717 default_tag("remove") {
718 attachable(anchor,spin, two_d=true, path=path, extent=false) {
719 polygon(path);
720 children();
721 }
722 }
723}
724
725
726
727function mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) =
728 assert(is_finite(size)||is_vector(size,2))
729 assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180)
730 assert(all_nonnegative([excess]))
731 let(
732 size = force_list(size,2),
733 top = polar_to_xy(size.y/sin(mask_angle),mask_angle),
734 bot = [top.x+size.x,0],
735 dummy=assert(top.x+size.x>=0, str("Rabbet of size ",size, " does not fit on ",mask_angle," corner.")),
736 outside_corner = _inset_corner([top,[0,0],bot],mask_angle, [0,0], excess, flat_top=true),
737 path =deduplicate([
738 each outside_corner[0],
739 outside_corner[1][0],
740 [outside_corner[1][2].x, outside_corner[1][0].y],
741 outside_corner[1][2]
742 ],closed=true)
743 ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
744
745
746// Function&Module: mask2d_dovetail()
747// Synopsis: Creates a 2D dovetail mask shape.
748// SynTags: Geom, Path
749// Topics: Masks (2D), Shapes (2D), Paths (2D), Path Generators, Attachable
750// See Also: corner_profile(), edge_profile(), face_profile()
751// Usage: As Module
752// mask2d_dovetail(edge, angle, [inset], [shelf], [excess], ...) [ATTACHMENTS];
753// mask2d_dovetail(width=, angle=, [inset=], [shelf=], [excess=], ...) [ATTACHMENTS];
754// mask2d_dovetail(height=, angle=, [inset=], [shelf=], [excess=], ...) [ATTACHMENTS];
755// mask2d_dovetail(width=, height=, [inset=], [shelf=], [excess=], ...) [ATTACHMENTS];
756// Usage: As Function
757// path = mask2d_dovetail(edge, [angle], [inset], [shelf], [excess]);
758// Description:
759// Creates a 2D dovetail mask shape that is useful for extruding into a 3D mask for a 90° edge.
760// Conversely, you can use that same extruded shape to make an interior dovetail between two walls at a 90º angle.
761// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
762// If called as a function, returns a 2D path of the outline of the mask shape.
763// Arguments:
764// edge = The length of the edge of the dovetail.
765// angle = The angle of the chamfer edge, away from vertical.
766// shelf = The extra height to add to the inside corner of the dovetail. Default: 0
767// inset = Optional amount to inset in perpendicular direction from each edge. Default: 0
768// mask_angle = Number of degrees in the corner angle to mask. Default: 90
769// excess = Extra amount of mask shape to creates on the X and quasi-Y sides of the shape. Default: 0.01
770// ---
771// width = The width of the dovetail (excluding any inset)
772// height = The height of the dovetail (excluding any inset or shelf).
773// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true.
774// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
775// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
776// Side Effects:
777// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
778// Example(2D): 2D Dovetail Mask
779// mask2d_dovetail(width=10,angle=14);
780// Example(2D): 2D Dovetail Mask by height and slope. A slope of 1/6 is a common choice.
781// mask2d_dovetail(height=20, slope=1/6);
782// Example(2D): 2D Inset Dovetail Mask to make the dovetail wider
783// mask2d_dovetail(width=5, angle=12, inset=[4,0]);
784// Example(2D): 2D Inset Dovetail Mask on an obtuse angle
785// mask2d_dovetail(width=5, mask_angle=110, angle=12);
786// Example(2D): 2D Inset Dovetail Mask on an acute angle will generally require an inset in order to fit.
787// mask2d_dovetail(width=5, mask_angle=70, angle=12, inset=[6,0]);
788// Example(2D): 2D dovetail mask by edge length and angle
789// mask2d_dovetail(edge=10,width=4);
790// Example(2D): 2D dovetail mask by width and height
791// mask2d_dovetail(width=5,height=25);
792// Example: Masking by Edge Attachment
793// diff()
794// cube([50,60,70],center=true)
795// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
796// mask2d_dovetail(width=10, angle=30, inset=2);
797// Example: Making an interior dovetail
798// %render() difference() {
799// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
800// cube(310, anchor=BOT+LEFT);
801// }
802// xrot(90)
803// linear_extrude(height=30, center=true)
804// mask2d_dovetail(width=10,angle=30);
805module mask2d_dovetail(edge, angle, shelf=0, inset=0, mask_angle=90, excess=0.01, flat_top=true, w,h,width,height, slope, anchor=CENTER, spin=0,x,y) {
806 path = mask2d_dovetail(w=w,width=width,h=h,height=height, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess, slope=slope, flat_top=flat_top, mask_angle=mask_angle,x=x,y=y);
807 default_tag("remove") {
808 attachable(anchor,spin, two_d=true, path=path) {
809 polygon(path);
810 children();
811 }
812 }
813}
814
815function mask2d_dovetail(edge, angle, slope, shelf=0, inset=0, mask_angle=90, excess=0.01, flat_top=true, w,width,h,height, anchor=CENTER, spin=0,x,y) =
816 assert(num_defined([slope,angle])<=1, "Cannot give both slope and angle")
817 assert(is_finite(excess))
818 assert(is_undef(w) || all_positive([w]))
819 assert(is_undef(h) || all_positive([h]))
820 assert(is_undef(height) || all_positive([height]))
821 assert(is_undef(width) || all_positive([width]))
822 assert(is_finite(inset)||is_vector(inset,2))
823 let(
824 y = one_defined([h,height,y],"h,height,y",dflt=undef),
825 x = one_defined([w,width,x],"w,width,x",dflt=undef),
826 angle = is_def(slope) ? atan(slope) : angle,
827 dummy2=//assert(num_defined([x,y])==2 || (all_positive([angle]) && angle<90), "Invalid angle or slope")
828 assert(num_defined([x,y])<2 || is_undef(angle), "Cannot give both width and height if you give slope or angle"),
829 inset = force_list(inset,2),
830 width = is_def(x)? x
831 : is_def(y)? adj_ang_to_opp(adj=y,ang=angle)
832 : assert(all_positive([edge]))
833 hyp_ang_to_opp(hyp=edge,ang=angle),
834 height = is_def(y) ? y
835 : num_defined([width,angle])==2 ? opp_ang_to_adj(opp=width,ang=angle)+shelf
836 : all_defined([edge,angle]) ? hyp_ang_to_adj(hyp=edge,ang=angle)
837 : assert(is_def(edge) && edge>width) sqrt(edge^2-width^2),
838 top = polar_to_xy(height/sin(mask_angle),mask_angle),
839 outside_corner = _inset_corner([top,[0,0],[0,0]], mask_angle, inset, excess, flat_top),
840 dummy=assert(outside_corner[1][1].x+width > top.x, "Dovetail doesn't fit on that angled edge. Try increasing x inset.")
841 assert(outside_corner[1][1].x>=0, "Dovetails doesn't fit on the edge. Try decreasing y inset."),
842 path = deduplicate([
843 each outside_corner[0],
844 outside_corner[1][0],
845 if (shelf>0) outside_corner[1][1]+[width,height],
846 outside_corner[1][1]+[width,height-shelf],
847 outside_corner[1][1]
848 ], closed=true)
849 ) reorient(anchor,spin, two_d=true, path=path, p=path);
850
851
852
853// Function&Module: mask2d_ogee()
854// Synopsis: Creates a 2D ogee mask shape.
855// SynTags: Geom, Path
856// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D)
857// See Also: corner_profile(), edge_profile(), face_profile()
858// Usage: As Module
859// mask2d_ogee(pattern, [excess], ...) [ATTAHCMENTS];
860// Usage: As Function
861// path = mask2d_ogee(pattern, [excess], ...);
862// Description:
863// Creates a 2D Ogee mask shape that is useful for extruding into a 3D mask for a 90° edge.
864// Conversely, you can use that same extruded shape to make an interior ogee decoration between two walls at a 90º angle.
865// As a 2D mask, this is designed to be differenced away from the edge of a shape that with its corner at the origin and one edge on the X+ axis and the other mask_angle degrees counterclockwise from the X+ axis.
866// Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern.
867// Patterns are given as TYPE, VALUE pairs. ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`. See Patterns below.
868// If called as a function, returns a 2D path of the outline of the mask shape.
869// .
870// ### Patterns
871// .
872// Type | Argument | Description
873// -------- | --------- | ----------------
874// "step" | [x,y] | Makes a line to a point `x` right and `y` down.
875// "xstep" | dist | Makes a `dist` length line towards X+.
876// "ystep" | dist | Makes a `dist` length line towards Y-.
877// "round" | radius | Makes an arc that will mask a roundover.
878// "fillet" | radius | Makes an arc that will mask a fillet.
879//
880// Arguments:
881// pattern = A list of pattern pieces to describe the Ogee.
882// excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01
883// ---
884// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
885// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
886//
887// Side Effects:
888// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
889//
890// Example(2D): 2D Ogee Mask
891// mask2d_ogee([
892// "xstep",1, "ystep",1, // Starting shoulder.
893// "fillet",5, "round",5, // S-curve.
894// "ystep",1, "xstep",1 // Ending shoulder.
895// ]);
896// Example: Masking by Edge Attachment
897// diff()
898// cube([50,60,70],center=true)
899// edge_profile(TOP)
900// mask2d_ogee([
901// "xstep",1, "ystep",1, // Starting shoulder.
902// "fillet",5, "round",5, // S-curve.
903// "ystep",1, "xstep",1 // Ending shoulder.
904// ]);
905// Example: Making an interior ogee
906// %render() difference() {
907// move(-[5,0,5]) cube(30, anchor=BOT+LEFT);
908// cube(310, anchor=BOT+LEFT);
909// }
910// xrot(90)
911// linear_extrude(height=30, center=true)
912// mask2d_ogee([
913// "xstep", 1, "round",5,
914// "ystep",1, "fillet",5,
915// "xstep", 1, "ystep", 1,
916// ]);
917module mask2d_ogee(pattern, excess=0.01, anchor=CENTER,spin=0) {
918 path = mask2d_ogee(pattern, excess=excess);
919 default_tag("remove") {
920 attachable(anchor,spin, two_d=true, path=path) {
921 polygon(path);
922 children();
923 }
924 }
925}
926
927function mask2d_ogee(pattern, excess=0.01, anchor=CENTER, spin=0) =
928 assert(is_list(pattern))
929 assert(len(pattern)>0)
930 assert(len(pattern)%2==0,"pattern must be a list of TYPE, VAL pairs.")
931 assert(all([for (i = idx(pattern,step=2)) in_list(pattern[i],["step","xstep","ystep","round","fillet"])]))
932 let(
933 x = concat([0], cumsum([
934 for (i=idx(pattern,step=2)) let(
935 type = pattern[i],
936 val = pattern[i+1]
937 ) (
938 type=="step"? val.x :
939 type=="xstep"? val :
940 type=="round"? val :
941 type=="fillet"? val :
942 0
943 )
944 ])),
945 y = concat([0], cumsum([
946 for (i=idx(pattern,step=2)) let(
947 type = pattern[i],
948 val = pattern[i+1]
949 ) (
950 type=="step"? val.y :
951 type=="ystep"? val :
952 type=="round"? val :
953 type=="fillet"? val :
954 0
955 )
956 ])),
957 tot_x = last(x),
958 tot_y = last(y),
959 data = [
960 for (i=idx(pattern,step=2)) let(
961 type = pattern[i],
962 val = pattern[i+1],
963 pt = [x[i/2], tot_y-y[i/2]] + (
964 type=="step"? [val.x,-val.y] :
965 type=="xstep"? [val,0] :
966 type=="ystep"? [0,-val] :
967 type=="round"? [val,0] :
968 type=="fillet"? [0,-val] :
969 [0,0]
970 )
971 ) [type, val, pt]
972 ],
973 path = [
974 [tot_x,-excess],
975 [-excess,-excess],
976 [-excess,tot_y],
977 for (pat = data) each
978 pat[0]=="step"? [pat[2]] :
979 pat[0]=="xstep"? [pat[2]] :
980 pat[0]=="ystep"? [pat[2]] :
981 let(
982 r = pat[1],
983 steps = segs(abs(r)),
984 step = 90/steps
985 ) [
986 for (i=[0:1:steps]) let(
987 a = pat[0]=="round"? (180+i*step) : (90-i*step)
988 ) pat[2] + abs(r)*[cos(a),sin(a)]
989 ]
990 ],
991 path2 = deduplicate(path)
992 ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
993
994
995// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap